package com.umeox.moto.watch.themes.widget.wheel

import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Rect
import android.graphics.Typeface
import android.os.Handler
import android.os.Message
import android.text.Layout
import android.text.StaticLayout
import android.text.TextPaint
import android.util.AttributeSet
import android.util.DisplayMetrics
import android.view.GestureDetector
import android.view.GestureDetector.SimpleOnGestureListener
import android.view.MotionEvent
import android.view.View
import android.view.WindowManager
import android.view.animation.Interpolator
import android.widget.Scroller
import androidx.core.content.res.ResourcesCompat
import com.umeox.moto.watch.themes.R
import java.util.LinkedList
import kotlin.math.abs
import kotlin.math.ceil
import kotlin.math.max
import kotlin.math.min


/**
 * Numeric wheel view.
 *
 * @author Yuri Kanivets
 */
class WheelView : View {
    /** Current value & label text color  */
    var valueTextColor = 0

    /** Items text color  */
    var itemTextColor = 0

    var valueTextSize: Int = 0
    var itemTextSize: Int = 0

    /** Left and right padding value  */
    @JvmField
    var PADDING: Int = 10

    // Widths
    private var itemsWidth = 0
    private var labelWidth = 0

    // Item height
    private var nodeHeight = 0

    // Text paints
    private var itemsPaint: TextPaint? = null
    private var valuePaint: TextPaint? = null

    // Layouts
    private var itemsLayout: StaticLayout? = null
    private var labelLayout: StaticLayout? = null
    private var valueLayout: StaticLayout? = null

    // private Drawable centerDrawable;
    // Scrolling
    private var isScrollingPerformed = false
    private var scrollingOffset = 0

    // Scrolling animation
    private var gestureDetector: GestureDetector? = null
    private var scroller: Scroller? = null
    private var lastScrollY = 0

    private var mItemFont: Typeface? = null
    private var mValueFont: Typeface? = null

    // Cyclic
    var cyclic: Boolean = false
        set(value) {
            field = value

            invalidate()
            invalidateLayouts()
        }

    // Listeners
    private val changingListeners: MutableList<OnWheelChangedListener> = LinkedList()
    private val scrollingListeners: MutableList<OnWheelScrollListener> = LinkedList()
    private val clickingListeners: MutableList<OnWheelClickedListener> = LinkedList()

    private var context: Context? = null

    /**
     * Constructor
     */
    constructor(context: Context, attrs: AttributeSet?, defStyle: Int) : super(context, attrs, defStyle) {
        initData(context, attrs)
    }

    /**
     * Constructor
     */
    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
        initData(context, attrs)
    }

    /**
     * Constructor
     */
    constructor(context: Context) : super(context) {
        initData(context, null)
    }


    /**
     * Initializes class data
     *
     * @param context the context
     * @param attrs
     */
    private fun initData(context: Context, attrs: AttributeSet?) {
        this.context = context

        @SuppressLint("Recycle") val typedArray = context.obtainStyledAttributes(attrs, R.styleable.WheelView)
        // noinspection TryFinallyCanBeTryWithResources
        try {
            val itemFontRes = typedArray.getResourceId(R.styleable.WheelView_itemFontFamily, 0)
            mItemFont = if (itemFontRes == 0) Typeface.DEFAULT else ResourcesCompat.getFont(context, itemFontRes)

            val valueFontRes = typedArray.getResourceId(R.styleable.WheelView_valueFontFamily, 0)
            mValueFont = if (itemFontRes == 0) Typeface.DEFAULT else ResourcesCompat.getFont(context, valueFontRes)

            itemTextSize = typedArray.getDimensionPixelSize(R.styleable.WheelView_itemTextSize, DEFAULT_ITEM_TEXT_SIZE)
            valueTextSize = typedArray.getDimensionPixelSize(R.styleable.WheelView_valueTextSize, DEFAULT_VALUE_TEXT_SIZE)

            nodeHeight = typedArray.getDimensionPixelSize(R.styleable.WheelView_nodeHeight, 0)
            visibleItems = typedArray.getInt(R.styleable.WheelView_visibleItemsCount, DEF_VISIBLE_ITEMS)

            valueTextColor = typedArray.getColor(R.styleable.WheelView_valueTextColor, Color.parseColor("#000000"))
            itemTextColor = typedArray.getColor(R.styleable.WheelView_itemTextColor, Color.parseColor("#666666"))
        } catch (ignore: Exception) {
        } finally {
            typedArray.recycle()
        }

        screenWidth

        //设置字体
        gestureDetector = GestureDetector(context, gestureListener)
        gestureDetector!!.setIsLongpressEnabled(false)

        scroller = Scroller(context)
    }

    fun setTextSize(textSizeSelected: Int, textSize: Int) {
        this.valueTextSize = textSizeSelected
        this.itemTextSize = textSize
    }

    var adapter: WheelAdapter? = null
        set(value) {
            field = value
            invalidateLayouts()
            invalidate()
        }

    /**
     * Set the the specified scrolling interpolator
     *
     * @param interpolator
     * the interpolator
     */
    fun setInterpolator(interpolator: Interpolator?) {
        scroller!!.forceFinished(true)
        scroller = Scroller(getContext(), interpolator)
    }

    // Count of visible items
    var visibleItems = 0
        set(value) {
            field = value
            invalidate()
        }


    // Label & background
    var label: String? = null
        set(value) {
            if (field == null || field != value) {
                field = value
                labelLayout = null
                invalidate()
            }
        }

    /**
     * Adds wheel changing listener
     *
     * @param listener
     * the listener
     */
    fun addChangingListener(listener: OnWheelChangedListener) {
        changingListeners.add(listener)
    }

    /**
     * Removes wheel changing listener
     *
     * @param listener
     * the listener
     */
    fun removeChangingListener(listener: OnWheelChangedListener) {
        changingListeners.remove(listener)
    }

    /**
     * Notifies changing listeners
     *
     * @param oldValue
     * the old wheel value
     * @param newValue
     * the new wheel value
     */
    protected fun notifyChangingListeners(oldValue: Int, newValue: Int) {
        // MainActivity.mHandler.sendEmptyMessage(1001);

        for (listener in changingListeners) {
            listener.onChanged(this, oldValue, newValue)
        }
    }

    /**
     * Adds wheel scrolling listener
     *
     * @param listener
     * the listener
     */
    fun addScrollingListener(listener: OnWheelScrollListener) {
        scrollingListeners.add(listener)
    }

    /**
     * Removes wheel scrolling listener
     *
     * @param listener
     * the listener
     */
    fun removeScrollingListener(listener: OnWheelScrollListener) {
        scrollingListeners.remove(listener)
    }

    /**
     * Notifies listeners about starting scrolling
     */
    protected fun notifyScrollingListenersAboutStart() {
        for (listener in scrollingListeners) {
            listener.onScrollingStarted(this)
        }
    }

    /**
     * Notifies listeners about ending scrolling
     */
    protected fun notifyScrollingListenersAboutEnd() {
        for (listener in scrollingListeners) {
            listener.onScrollingFinished(this)
        }
    }

    var currentItem = 0
        private set

    fun setCurrentItem(index: Int) = setCurrentItem(index, false)
    fun setCurrentItem(index: Int, animated: Boolean = false) {
        var index = index
        if (adapter == null || adapter!!.itemsCount == 0) {
            return  // throw?
        }
        if (index < 0 || index >= adapter!!.itemsCount) {
            if (cyclic) {
                while (index < 0) {
                    index += adapter!!.itemsCount
                }
                index %= adapter!!.itemsCount
            } else {
                return  // throw?
            }
        }
        if (index != currentItem) {
            if (animated) {
                scroll(index - currentItem, SCROLLING_DURATION)
            } else {
                invalidateLayouts()

                val old = currentItem
                currentItem = index

                notifyChangingListeners(old, currentItem)

                invalidate()
            }
        }
    }

    /**
     * Invalidates layouts
     */
    private fun invalidateLayouts() {
        itemsLayout = null
        valueLayout = null
        scrollingOffset = 0
    }

    private var screen_width = 0 // 屏幕宽度

    val screenWidth: Int
        /**
         * 获得宽度
         */
        get() {
            if (screen_width == 0) {
                val dm = DisplayMetrics()
                val wm = context!!.getSystemService(Context.WINDOW_SERVICE) as WindowManager
                wm.defaultDisplay.getMetrics(dm)
                screen_width = dm.widthPixels
                // System.out.println("宽度是    " + screen_width);
            }
            return screen_width
        }

    @JvmField
    var boldFlag: Boolean = true

    /**
     * Initializes resources
     */
    private fun initResourcesIfNecessary() {
        // int width1 = screen_width / 3 / 4 - 14 + 2;
        // int width2 = screen_width / 3 / 4 - 18 + 2;
        val width1 = screen_width / 3 / 4 + 2
        val width2 = screen_width / 3 / 4 + 2

        //		LogUtils.w(width1 + "/" + width2);

//		TEXT_SIZE_SELECTED = width1 > DEFAULT_TEXT_SIZE_SELECTED ? DEFAULT_TEXT_SIZE_SELECTED : width1;
//		TEXT_SIZE = width2 > DEFAULT_TEXT_SIZE ? DEFAULT_TEXT_SIZE : width2;
        if (itemsPaint == null) {
            var flag = Paint.ANTI_ALIAS_FLAG
            if (boldFlag) flag = flag or Paint.FAKE_BOLD_TEXT_FLAG
            itemsPaint = TextPaint(flag)
            itemsPaint!!.textSize = itemTextSize.toFloat()
            itemsPaint!!.setTypeface(mItemFont)
        }

        if (valuePaint == null) {
            var flag = Paint.ANTI_ALIAS_FLAG or Paint.DITHER_FLAG
            if (boldFlag) flag = flag or Paint.FAKE_BOLD_TEXT_FLAG
            valuePaint = TextPaint(flag or Paint.DITHER_FLAG)
            valuePaint!!.density = resources.displayMetrics.density
            valuePaint!!.textSize = valueTextSize.toFloat()
            valuePaint!!.setShadowLayer(0.1f, 0f, 0.1f, -0x3f3f40)
            valuePaint!!.setTypeface(mValueFont)
        }
    }

    fun resetPaints() {
        itemsPaint = null
        valuePaint = null
        initResourcesIfNecessary()
    }

    /**
     * Calculates desired height for layout
     *
     * @param layout
     * the source layout
     * @return the desired layout height
     */
    private fun getDesiredHeight(layout: Layout?): Int {
        if (layout == null) {
            return 0
        }

        var desired = getNodeHeight() * visibleItems

        // Check against our minimum height
        desired = max(desired.toDouble(), suggestedMinimumHeight.toDouble()).toInt()

        return desired
    }

    /**
     * Returns text item by index
     *
     * @param index
     * the item index
     * @return the item or null
     */
    private fun getTextItem(index: Int): String? {
        var index = index
        if (adapter == null || adapter!!.itemsCount == 0) {
            return null
        }
        val count = adapter!!.itemsCount
        if ((index < 0 || index >= count) && !cyclic) {
            return null
        } else {
            while (index < 0) {
                index = count + index
            }
        }

        index %= count
        return adapter!!.getItem(index) // 返回index对应的内容
    }

    /**
     * Builds text depending on current value
     *
     * @param useCurrentValue
     * @return the text
     */
    private fun buildText(useCurrentValue: Boolean): String {
        val itemsText = StringBuilder()
        val addItems = visibleItems / 2 + 1

        for (i in currentItem - addItems..currentItem + addItems) {
            if (useCurrentValue || i != currentItem) {
                val text = getTextItem(i)

                if (text != null) {
                    itemsText.append(text)
                }
            }
            if (i < currentItem + addItems) {
                itemsText.append("\n")
            }
        }

        return itemsText.toString()
    }

    private val maxTextLength: Int
        /**
         * Returns the max item length that can be present
         *
         * @return the max length
         */
        get() {
            val adapter = this.adapter ?: return 0

            val adapterLength = adapter.maximumLength
            if (adapterLength > 0) {
                return adapterLength
            }

            var maxText: String? = null
            val addItems = visibleItems / 2
            val start = max(currentItem - addItems, 0)
            val end = min(currentItem + visibleItems, adapter.itemsCount)
            for (i in start until end) {
                val text = adapter.getItem(i)
                if (text != null && (maxText == null || maxText.length < text.length)) {
                    maxText = text
                }
            }

            return maxText?.length ?: 0
        }

    /**
     * Returns height of wheel item
     *
     * @return the item height
     */
    private fun getNodeHeight(): Int {
        if (nodeHeight != 0) {
            return nodeHeight
        } else if (itemsLayout != null && itemsLayout!!.lineCount > 2) {
            nodeHeight = itemsLayout!!.getLineTop(1) - itemsLayout!!.getLineTop(0)
            return nodeHeight * visibleItems
        } else {
            nodeHeight = max(itemTextSize, valueTextSize)
            return nodeHeight
        }
    }

    /**
     * Calculates control width and creates text layouts
     *
     * @param widthSize
     * the input layout width
     * @param mode
     * the layout mode
     * @return the calculated control width
     */
    private fun calculateLayoutWidth(widthSize: Int, mode: Int): Int {
        initResourcesIfNecessary()

        var width: Int

        val maxLength = maxTextLength
        if (maxLength > 0) {
            val textWidth = ceil(Layout.getDesiredWidth("0", itemsPaint).toDouble()).toFloat()
            val valueWidth = ceil(Layout.getDesiredWidth("0", valuePaint).toDouble()).toFloat()
            itemsWidth = (maxLength * max(textWidth, valueWidth)).toInt()
        } else {
            itemsWidth = 0
        }

        labelWidth = 0
        if (label != null && label!!.isNotEmpty()) {
            labelWidth = ceil(Layout.getDesiredWidth(label, valuePaint).toDouble()).toInt()
        }

        var recalculate = false
        if (mode == MeasureSpec.EXACTLY) {
            width = widthSize
            recalculate = true
        } else {
            width = itemsWidth + labelWidth + 2 * PADDING
            if (labelWidth > 0) {
                width += LABEL_OFFSET
            }

            // Check against our minimum width
            width = max(width.toDouble(), suggestedMinimumWidth.toDouble()).toInt()

            if (mode == MeasureSpec.AT_MOST && widthSize < width) {
                width = widthSize
                recalculate = true
            }
        }

        if (recalculate) {
            // recalculate width
            val pureWidth = width - LABEL_OFFSET - 2 * PADDING
            if (pureWidth <= 0) {
                labelWidth = 0
                itemsWidth = 0
            }
            if (labelWidth > 0) {
                val newWidthItems = itemsWidth.toDouble() * pureWidth / (itemsWidth + labelWidth)
                itemsWidth = newWidthItems.toInt()
                labelWidth = pureWidth - itemsWidth
            } else {
                itemsWidth = pureWidth + LABEL_OFFSET // no label
            }
        }

        if (itemsWidth > 0) {
            createLayouts(itemsWidth, labelWidth)
        }

        return width
    }

    @JvmField
    var textAlign: Layout.Alignment? = null

    /**
     * Creates layouts
     *
     * @param widthItems
     * width of items layout
     * @param widthLabel
     * width of label layout
     */
    private fun createLayouts(widthItems: Int, widthLabel: Int) {
        var align: Layout.Alignment? = if (widthLabel > 0) Layout.Alignment.ALIGN_OPPOSITE else Layout.Alignment.ALIGN_CENTER
        val itemsPaint = itemsPaint ?: return
        val valuePaint = valuePaint ?: return
        if (textAlign != null) align = textAlign
        if (itemsLayout == null || itemsLayout!!.width > widthItems) {
            val text = buildText(isScrollingPerformed)
            itemsLayout = StaticLayout(
                text, itemsPaint, widthItems,
                align, 1f, (nodeHeight - itemsPaint.fontSpacing), false
            )
        } else {
            itemsLayout!!.increaseWidthTo(widthItems)
        }

        if (!isScrollingPerformed && (valueLayout == null || valueLayout!!.width > widthItems)) {
            val text = if (adapter != null) adapter!!.getItem(currentItem) else null
            val str = text ?: ""
            valueLayout = StaticLayout(str, valuePaint, widthItems, align, 1f, (nodeHeight - valuePaint.fontSpacing), false)
        } else if (isScrollingPerformed) {
            valueLayout = null
        } else {
            valueLayout!!.increaseWidthTo(widthItems)
        }

        if (widthLabel > 0) {
            if (labelLayout == null || labelLayout!!.width > widthLabel) {
                labelLayout = StaticLayout(
                    label, valuePaint, widthLabel, Layout.Alignment.ALIGN_NORMAL,
                    1f, 0f, false
                )
            } else {
                labelLayout!!.increaseWidthTo(widthLabel)
            }
        }
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        val widthMode = MeasureSpec.getMode(widthMeasureSpec)
        val heightMode = MeasureSpec.getMode(heightMeasureSpec)
        val widthSize = MeasureSpec.getSize(widthMeasureSpec)
        val heightSize = MeasureSpec.getSize(heightMeasureSpec)

        val width = calculateLayoutWidth(widthSize, widthMode)

        var height: Int
        if (heightMode == MeasureSpec.EXACTLY) {
            height = heightSize
        } else {
            height = getDesiredHeight(itemsLayout)
            if (heightMode == MeasureSpec.AT_MOST) {
                height = min(height.toDouble(), heightSize.toDouble()).toInt()
            }
        }

        setMeasuredDimension(width, height)
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)

        if (itemsLayout == null) {
            if (itemsWidth == 0) {
                calculateLayoutWidth(width, MeasureSpec.EXACTLY)
            } else {
                createLayouts(itemsWidth, labelWidth)
            }
        }

        if (itemsWidth > 0) {
            canvas.save()
            // Skip padding space and hide a part of top and bottom items
            canvas.translate(PADDING.toFloat(), 0f)
            drawItems(canvas)
            drawValue(canvas)
            canvas.restore()
        }

        // drawCenterRect(canvas);
    }

    /**
     * Draws value and label layout
     *
     * @param canvas
     * the canvas for drawing
     */
    private fun drawValue(canvas: Canvas) {
        val itemsLayout = itemsLayout ?: return
        valuePaint?.color = valueTextColor
        valuePaint?.drawableState = drawableState

        // draw current value
        if (valueLayout != null) {
            canvas.save()
            val valueDy = scrollingOffset + (visibleItems / 2) * nodeHeight + (nodeHeight - valuePaint!!.fontSpacing) / 2
            canvas.translate(0f, valueDy)
            valueLayout!!.draw(canvas)
            canvas.restore()

            // draw label
            if (labelLayout != null) {
                canvas.save()
                val valueBounds = Rect().apply { valueLayout!!.getLineBounds(0, this) }
                val labelDx = (itemsLayout.width + LABEL_OFFSET).toFloat()
                val labelDy = valueBounds.top.toFloat()
                canvas.translate(labelDx, labelDy)
                labelLayout!!.draw(canvas)
                canvas.restore()
            }
        }
    }

    /**
     * Draws items
     *
     * @param canvas
     * the canvas for drawing
     */
    private fun drawItems(canvas: Canvas) {
        val itemsPaint = itemsPaint ?: return
        val itemsLayout = itemsLayout ?: return
        canvas.save()
        val top = -getNodeHeight()
        canvas.translate(0f, top + scrollingOffset + (nodeHeight - itemsPaint.fontSpacing) / 2)

        itemsPaint.color = itemTextColor
        itemsPaint.drawableState = drawableState
        itemsLayout.draw(canvas)

        canvas.restore()
    }

    override fun onTouchEvent(event: MotionEvent): Boolean {
        adapter ?: return true

        if (event.action == MotionEvent.ACTION_UP) {
            if (!isScrollingPerformed) {
                var distance = event.y.toInt() - height / 2
                if (distance > 0) {
                    distance += getNodeHeight() / 2
                } else {
                    distance -= getNodeHeight() / 2
                }
                val items = distance / getNodeHeight()
                if (items != 0 && isValidItemIndex(currentItem + items)) {
                    notifyClickListenersAboutClick(currentItem + items)
                }
            }
        }

        if (!gestureDetector!!.onTouchEvent(event) && event.action == MotionEvent.ACTION_UP) {
            justify()
        }
        return true
    }

    /**
     * Checks whether intem index is valid
     *
     * @param index
     * the item index
     * @return true if item index is not out of bounds or the wheel is cyclic
     */
    private fun isValidItemIndex(index: Int): Boolean {
        return adapter != null && adapter!!.itemsCount > 0 && (cyclic || index >= 0 && index < adapter!!.itemsCount)
    }

    /**
     * Scrolls the wheel
     *
     * @param delta
     * the scrolling value
     */
    private fun doScroll(delta: Int) {
        scrollingOffset += delta

        var count = scrollingOffset / getNodeHeight()
        var pos = currentItem - count
        if (cyclic && adapter!!.itemsCount > 0) {
            // fix position by rotating
            while (pos < 0) {
                pos += adapter!!.itemsCount
            }
            pos %= adapter!!.itemsCount
        } else if (isScrollingPerformed) {
            //
            if (pos < 0) {
                count = currentItem
                pos = 0
            } else if (pos >= adapter!!.itemsCount) {
                count = currentItem - adapter!!.itemsCount + 1
                pos = adapter!!.itemsCount - 1
            }
        } else {
            // fix position
            pos = max(pos.toDouble(), 0.0).toInt()
            pos = min(pos.toDouble(), (adapter!!.itemsCount - 1).toDouble()).toInt()
        }

        val offset = scrollingOffset
        if (pos != currentItem) {
            setCurrentItem(pos, false)
        } else {
            invalidate()
        }

        // update offset
        scrollingOffset = offset - count * getNodeHeight()
        if (scrollingOffset > height) {
            scrollingOffset = scrollingOffset % height + height
        }
    }

    // gesture listener
    private val gestureListener: SimpleOnGestureListener = object : SimpleOnGestureListener() {
        override fun onDown(e: MotionEvent): Boolean {
            if (isScrollingPerformed) {
                scroller!!.forceFinished(true)
                clearMessages()
                return true
            }
            return false
        }

        override fun onScroll(e1: MotionEvent?, e2: MotionEvent, distanceX: Float, distanceY: Float): Boolean {
            startScrolling()
            doScroll(-distanceY.toInt())
            return true
        }

        override fun onFling(e1: MotionEvent?, e2: MotionEvent, velocityX: Float, velocityY: Float): Boolean {
            lastScrollY = currentItem * getNodeHeight() + scrollingOffset
            val maxY = if (cyclic) 0x7FFFFFFF else adapter!!.itemsCount * getNodeHeight()
            val minY = if (cyclic) -maxY else 0
            scroller!!.fling(0, lastScrollY, 0, -velocityY.toInt() / 2, 0, 0, minY, maxY)
            setNextMessage(MESSAGE_SCROLL)
            return true
        }
    }

    // Messages
    private val MESSAGE_SCROLL = 0
    private val MESSAGE_JUSTIFY = 1

    /**
     * Set next message to queue. Clears queue before.
     *
     * @param message
     * the message to set
     */
    private fun setNextMessage(message: Int) {
        clearMessages()
        animationHandler.sendEmptyMessage(message)
    }

    /**
     * Clears messages from queue
     */
    private fun clearMessages() {
        animationHandler.removeMessages(MESSAGE_SCROLL)
        animationHandler.removeMessages(MESSAGE_JUSTIFY)
    }

    // animation handler
    private val animationHandler: Handler = object : Handler() {
        override fun handleMessage(msg: Message) {
            scroller!!.computeScrollOffset()
            var currY = scroller!!.currY
            val delta = lastScrollY - currY
            lastScrollY = currY
            if (delta != 0) {
                doScroll(delta)
            }

            // scrolling is not finished when it comes to final Y
            // so, finish it manually
            if (abs((currY - scroller!!.finalY).toDouble()) < MIN_DELTA_FOR_SCROLLING) {
                currY = scroller!!.finalY
                scroller!!.forceFinished(true)
            }
            if (!scroller!!.isFinished) {
                sendEmptyMessage(msg.what)
            } else if (msg.what == MESSAGE_SCROLL) {
                justify()
            } else {
                finishScrolling()
            }
        }
    }

    /**
     * Justifies wheel
     */
    private fun justify() {
        if (adapter == null) {
            return
        }

        lastScrollY = 0
        var offset = scrollingOffset
        val itemHeight = getNodeHeight()
        val needToIncrease = if (offset > 0) currentItem < adapter!!.itemsCount else currentItem > 0
        if ((cyclic || needToIncrease) && abs(offset.toFloat().toDouble()) > itemHeight.toFloat() / 2) {
            if (offset < 0) offset += itemHeight + MIN_DELTA_FOR_SCROLLING
            else offset -= itemHeight + MIN_DELTA_FOR_SCROLLING
        }
        if (abs(offset.toDouble()) > MIN_DELTA_FOR_SCROLLING) {
            scroller!!.startScroll(0, 0, 0, offset, SCROLLING_DURATION)
            setNextMessage(MESSAGE_JUSTIFY)
        } else {
            finishScrolling()
        }
    }

    /**
     * Starts scrolling
     */
    private fun startScrolling() {
        if (!isScrollingPerformed) {
            isScrollingPerformed = true
            notifyScrollingListenersAboutStart()
        }
    }

    /**
     * Finishes scrolling
     */
    fun finishScrolling() {
        if (isScrollingPerformed) {
            notifyScrollingListenersAboutEnd()
            isScrollingPerformed = false
        }
        invalidateLayouts()
        invalidate()
    }

    /**
     * Scroll the wheel
     *
     * @param time scrolling duration
     */
    fun scroll(itemsToScroll: Int, time: Int) {
        scroller!!.forceFinished(true)

        lastScrollY = scrollingOffset
        val offset = itemsToScroll * getNodeHeight()

        scroller!!.startScroll(0, lastScrollY, 0, offset - lastScrollY, time)
        setNextMessage(MESSAGE_SCROLL)

        startScrolling()
    }

    /**
     * Adds wheel clicking listener
     *
     * @param listener
     * the listener
     */
    fun addClickingListener(listener: OnWheelClickedListener) {
        clickingListeners.add(listener)
    }

    /**
     * Removes wheel clicking listener
     *
     * @param listener
     * the listener
     */
    fun removeClickingListener(listener: OnWheelClickedListener) {
        clickingListeners.remove(listener)
    }

    /**
     * Notifies listeners about clicking
     */
    protected fun notifyClickListenersAboutClick(item: Int) {
        for (listener in clickingListeners) {
            listener.onItemClicked(this, item)
        }
    }

    companion object {
        /** Scrolling duration  */
        private const val SCROLLING_DURATION = 400

        /** Minimum delta for scrolling  */
        private const val MIN_DELTA_FOR_SCROLLING = 1

        /** Top and bottom shadows colors  */
        private val SHADOWS_COLORS = intArrayOf(-0xeeeeef, 0x00AAAAAA, 0x00AAAAAA)

        /** Text size  */
        const val DEFAULT_VALUE_TEXT_SIZE: Int = 48 // 35
        const val DEFAULT_ITEM_TEXT_SIZE: Int = 38 // 30

        /** Additional width for items layout  */
        private const val ADDITIONAL_ITEMS_SPACE = 10

        /** Label offset  */
        private const val LABEL_OFFSET = 8

        /** Default count of visible items  */
        private const val DEF_VISIBLE_ITEMS = 3
    }
}
